import util from "util";

test("postMessage results in correct event", done => {
  let c1 = new BroadcastChannel("eventType");
  let c2 = new BroadcastChannel("eventType");

  c2.onmessage = (e: MessageEvent) => {
    expect(e).toBeInstanceOf(MessageEvent);
    expect(e.target).toBe(c2);
    expect(e.type).toBe("message");
    expect(e.origin).toBe("");
    expect(e.data).toBe("hello world");
    expect(e.source).toBe(null);
    c1.close();
    c2.close();
    done();
  };

  c1.postMessage("hello world");
});

test("broadcast channel properties", () => {
  let c1 = new BroadcastChannel("props");
  expect(c1.name).toBe("props");
  expect(c1.onmessage).toBe(null);
  expect(c1.onmessageerror).toBe(null);
  expect(c1.close).toBeInstanceOf(Function);
  expect(c1.postMessage).toBeInstanceOf(Function);
  expect(c1.ref).toBeInstanceOf(Function);
  expect(c1.unref).toBeInstanceOf(Function);
  c1.close();
});

test("broadcast channel worker wait", done => {
  var worker = new Worker(new URL("broadcast-channel-worker-simple.ts", import.meta.url).href);
  worker.ref();
  Bun.sleepSync(500);
  var bc = new BroadcastChannel("sleep");
  bc.onmessage = (e: MessageEvent) => {
    expect(e.data).toBe("done!");
    bc.close();
    worker.unref();
    done();
  };
  bc.postMessage("rise and shine!");
});

test("messages are delivered in port creation order", done => {
  let c1 = new BroadcastChannel("order");
  let c2 = new BroadcastChannel("order");
  let c3 = new BroadcastChannel("order");

  let events: MessageEvent[] = [];
  let doneCount = 0;
  let handler = (e: MessageEvent) => {
    events.push(e);
    if (e.data == "done") {
      doneCount++;
      if (doneCount == 2) {
        expect(events.length).toBe(6);
        expect(events[0].target).toBe(c2);
        expect(events[0].data).toBe("from c1");
        expect(events[1].target).toBe(c3);
        expect(events[1].data).toBe("from c1");
        expect(events[2].target).toBe(c1);
        expect(events[2].data).toBe("from c3");
        expect(events[3].target).toBe(c2);
        expect(events[3].data).toBe("from c3");
        expect(events[4].target).toBe(c1);
        expect(events[4].data).toBe("done");
        expect(events[5].target).toBe(c3);
        expect(events[5].data).toBe("done");
        c1.close();
        c2.close();
        c3.close();
        done();
      }
    }
  };

  c1.onmessage = handler;
  c2.onmessage = handler;
  c3.onmessage = handler;

  c1.postMessage("from c1");
  c3.postMessage("from c3");
  c2.postMessage("done");
});

test("messages aren't deliverd to a closed port.", done => {
  let c1 = new BroadcastChannel("closed");
  let c2 = new BroadcastChannel("closed");
  let c3 = new BroadcastChannel("closed");

  c2.onmessage = () => {
    expect().fail();
  };
  c2.close();
  c3.onmessage = () => {
    c1.close();
    c3.close();
    done();
  };
  c1.postMessage("test");
});

test("close broadcast channel and create another with the same name", done => {
  let c1 = new BroadcastChannel("close-and-create");
  c1.close();
  let c2 = new BroadcastChannel("close-and-create");
  let c3 = new BroadcastChannel("close-and-create");
  c2.onmessage = (e: MessageEvent) => {
    expect(e.data).toBe("done");
    c2.close();
    c3.close();
    done();
  };
  c3.postMessage("done");
});

test("messages aren't delivered to a port closed after calling postMessage.", done => {
  let c1 = new BroadcastChannel("closed");
  let c2 = new BroadcastChannel("closed");
  let c3 = new BroadcastChannel("closed");

  c2.onmessage = () => expect().fail();
  c3.onmessage = () => {
    c1.close();
    c3.close();
    done();
  };
  c1.postMessage("test");
  c2.close();
});

test("closing and creating channels during message delivery works correctly.", done => {
  let c1 = new BroadcastChannel("create-in-onmessage");
  let c2 = new BroadcastChannel("create-in-onmessage");

  c2.onmessage = (e: MessageEvent) => {
    expect(e.data).toBe("first");
    c2.close();
    let c3 = new BroadcastChannel("create-in-onmessage");
    c3.onmessage = (event: MessageEvent) => {
      expect(event.data).toBe("done");
      c1.close();
      c3.close();
      done();
    };
    c1.postMessage("done");
  };
  c1.postMessage("first");
  c2.postMessage("second");
});

test("Closing a channel in onmessage prevents already queued tasks from firing onmessage events", done => {
  let c1 = new BroadcastChannel("close-in-onmessage");
  let c2 = new BroadcastChannel("close-in-onmessage");
  let c3 = new BroadcastChannel("close-in-onmessage");

  let events: string[] = [];
  c1.onmessage = (e: MessageEvent) => events.push("c1: " + e.data);
  c2.onmessage = (e: MessageEvent) => events.push("c2: " + e.data);
  c3.onmessage = (e: MessageEvent) => events.push("c3: " + e.data);

  // c2 closes itself when it receives the first message
  c2.addEventListener("message", (e: MessageEvent) => {
    c2.close();
  });

  c3.addEventListener("message", (e: MessageEvent) => {
    if (e.data == "done") {
      expect(events).toEqual(["c2: first", "c3: first", "c3: done"]);
      c1.close();
      c3.close();
      done();
    }
  });
  c1.postMessage("first");
  c1.postMessage("done");
});

test("broadcast channel used with workers", async () => {
  const batchSize = 1;
  for (let total = 0; total < 100; total++) {
    let bc = new BroadcastChannel("hello test");

    let promises: Promise<void>[] = [];
    let resolveFns = [];

    for (var i = 0; i < batchSize; i++) {
      const { promise, resolve } = Promise.withResolvers();
      promises.push(promise);
      resolveFns.push(resolve);
    }
    bc.onmessage = (e: MessageEvent) => {
      expect(e).toBeInstanceOf(MessageEvent);
      expect(e.target).toBe(bc);
      expect(e.data).toBe("hello from worker");
      const resolve = resolveFns.shift();
      if (resolve) {
        resolve();
      } else {
        console.warn("resolve fn not found");
      }
      console.count("resolve fn called");
    };

    for (let i = 0; i < batchSize; i++) {
      new Worker(new URL("./broadcast-channel-worker.ts", import.meta.url).href);
    }

    await Promise.all(promises);
    bc.close();
    console.count("Batch complete");
  }
}, 99999);

test("user options are forwarded through custom inspect", () => {
  const bc = new BroadcastChannel("hello");
  expect(util.inspect(bc, { compact: true, breakLength: 2 })).toBe(
    "BroadcastChannel { name:\n   'hello',\n  active:\n   true }",
  );
});
